Skip to main content
Version: 1.524.0

Usage

Basic Example​

import 'package:flutter/material.dart';
import 'package:trustchex_flutter_sdk/trustchex_flutter_sdk.dart';

class VerificationScreen extends StatelessWidget {
final String sessionId;
final String baseUrl;

const VerificationScreen({
super.key,
required this.sessionId,
required this.baseUrl,
});

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Verification')),
body: TrustchexView(
sessionId: sessionId,
baseUrl: baseUrl,
onCompleted: () {
Navigator.of(context).pop();
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Verification completed successfully!'),
backgroundColor: Colors.green,
),
);
},
onError: (error) {
Navigator.of(context).pop();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Verification error: $error'),
backgroundColor: Colors.red,
),
);
},
),
);
}
}

Props​

PropTypeRequiredDescription
baseUrlString✅Your API base URL
sessionIdString❌Session identifier
brandingTrustchexBranding❌Colors and logo
localeTrustchexLocale❌Language (en, tr)
onCompletedVoidCallback❌Called when the verification process finishes
onErrorFunction(String)❌Error callback
onDocumentReadFunction(DocumentReadResult)❌Called when document is read (NFC or OCR)
skipNfcResultScreenbool❌Skip the NFC document review screen and proceed automatically after a successful NFC scan. Default: false
skipSuccessScreenbool❌Skip the final success screen and call onCompleted immediately when the process finishes. Default: false

Document Read Event​

The onDocumentRead callback fires as soon as document data is available — immediately after a successful NFC chip read or after a successful camera OCR scan. It receives a unified DocumentReadResult object regardless of source.

import 'package:trustchex_flutter_sdk/trustchex_flutter_sdk.dart';

TrustchexView(
baseUrl: 'https://your-api.com',
sessionId: 'session-123',
onDocumentRead: (result) {
final document = result.document;

print(document.documentType.name); // "passport" | "id"
print(document.issuingCountry); // "DEU"
print(document.nationality); // "DEU"
print(document.documentNumber); // "C01X00T478"
print(document.personalNumber); // "8512310074" | null
print(document.lastName); // "MÜLLER"
print(document.firstName); // "JÜRGEN KARL"
print(document.sex.name); // "male" | "female" | "unspecified" | "unknown"
print(document.dateOfBirth); // "1985-03-10" (ISO 8601)
print(document.dateOfExpiry); // "2030-11-05" (ISO 8601)
},
onCompleted: () => print('Done'),
onError: (error) => print('Error: $error'),
)

DocumentReadResult​

class DocumentReadResult {
final DocumentData document;
final DocumentName name;
final DocumentFaceImage? face;
}

class DocumentData {
final ScannedDocumentType documentType; // .passport | .idCard
final String issuingCountry; // 3-letter ICAO code, e.g. "TUR"
final String nationality;
final String documentNumber;
final String? personalNumber;
final String lastName; // Best available name, e.g. "MÜLLER"
final String firstName; // Best available name, e.g. "JÜRGEN"
final DocumentSex sex; // .male | .female | .unspecified
final String? dateOfBirth; // ISO 8601, e.g. "1990-05-15"
final String? dateOfExpiry; // ISO 8601, e.g. "2028-03-01"
}

class DocumentName {
final String rawLast; // MRZ ASCII surname
final String rawFirst; // MRZ ASCII given name
final String displayLast; // Best Unicode form
final String displayFirst; // Best Unicode form
final DocumentNameSource source; // .dg11 | .reverseTable | .raw
}

class DocumentFaceImage {
final String data; // Base64-encoded image
final String mimeType; // e.g. "image/jpeg"
}

Name Sources​

SourceDescription
dg11Exact printed name read from the NFC chip's DG11 file (UTF-8). Highest accuracy.
reverseTableReconstructed via ICAO 9303 reverse transliteration (e.g. OE→Ö, UE→Ü). Applied for known countries.
rawNo conversion — display equals the raw MRZ ASCII value.

Flow Control​

Skip NFC Result Screen​

By default, after a successful NFC chip read the SDK shows a review screen where the user can verify their document data before proceeding. Set skipNfcResultScreen to bypass this screen entirely — the SDK advances automatically as soon as the NFC scan completes.

TrustchexView(
baseUrl: 'https://your-api.com',
sessionId: 'session-123',
skipNfcResultScreen: true,
onCompleted: () => print('Done'),
onError: (error) => print('Error: $error'),
)

Use onDocumentRead alongside this prop if you need to access the scanned document data without showing the review screen.

Skip Success Screen​

By default, after the verification process completes the SDK shows a success animation for 3 seconds before calling onCompleted. Set skipSuccessScreen to call onCompleted immediately with no animation or delay.

TrustchexView(
baseUrl: 'https://your-api.com',
sessionId: 'session-123',
skipSuccessScreen: true,
onCompleted: () {
// Called immediately when verification is complete
Navigator.of(context).pop();
},
onError: (error) => print('Error: $error'),
)

Both props can be combined:

TrustchexView(
baseUrl: 'https://your-api.com',
sessionId: 'session-123',
skipNfcResultScreen: true,
skipSuccessScreen: true,
onDocumentRead: (result) => print(result.name.displayFirst),
onCompleted: () => Navigator.of(context).pop(),
onError: (error) => print('Error: $error'),
)

Accepted Document Types​

Which documents a user may scan is set per workflow, not in the app. In the dashboard, open Workflows, edit the Identity Document Scan (or Identity Document eID Scan) step, and choose Allowed Document Types:

SettingWhat the user can scan
None selected (default)ID card or passport
Identity CardID cards only
PassportPassports only

No SDK parameter or code change is needed. The SDK reads the setting from the verification session and adapts the scan screen:

  • Guide and instructions. The scan frame shows a drawing of the document to hold up, and the instruction names it ("Show the front of the card", "Show the photo page"). When both types are allowed, the guide alternates between an ID card and a passport until the document is recognised.
  • Other documents are rejected on the spot. A document that isn't allowed is never captured. The user is asked for the right one instead ("Show your ID card" or "Show your passport"), and scanning continues as soon as an allowed document is shown.
  • ID cards: front first. For ID cards, the front is always read before the back. Showing the back first prompts the user to turn the card over.

Allowed Countries, on the same step, limits documents by issuing country. That check runs when the document's MRZ is read: a document from another country is not captured, the user is told the country isn't supported, and scanning continues so another document can be shown.

With these checks, a user can't complete the step with a document the workflow doesn't accept.

With Branding​

TrustchexView(
sessionId: sessionId,
baseUrl: 'https://api.trustchex.com',
locale: TrustchexLocale.en,
branding: const TrustchexBranding(
logoUrl: 'https://trustchex.com/logo.png',
primaryColor: Color(0xFF1E40AF),
secondaryColor: Color(0xFFF8FAFC),
tertiaryColor: Color(0xFFDC2626),
),
onCompleted: () {
Navigator.of(context).pop();
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Verification completed successfully!'),
backgroundColor: Colors.green,
),
);
},
onError: (error) {
Navigator.of(context).pop();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Verification error: $error'),
backgroundColor: Colors.red,
),
);
},
)

MASAK Tebliğ 32 (Art. 4/C)​

For remote KYC of non-Turkish nationals via NFC passport, the SDK runs the Article 4/C checks whenever the server-side workflow enables them per step. The relevant step flags are:

FlagEffect
passiveAuthRun Passive Authentication on the eID scan (read EF.SOD, verify DG-hash integrity on-device, send the SOD for server-side signature + CSCA chaining).
enforceChipVizMatchServer terminal-rejects when the chip↔VIZ verdict is not MATCH.
collectGeolocationCapture a coarse device geolocation during the contract step. See below.
addressProofRequiredRequire a proof document in the address-capture step.
blockedNationalitiesServer rejects documents whose nationality is on the list.
requireOperatorRequire a connected operator in the video interview.
enforcePassiveAuthServer terminal-rejects when Passive Auth comes back TAMPERED.

These are driven entirely by the backend — no client code is required to turn them on. The SDK computes the chip↔VIZ verdict and Passive Authentication integrity on-device and sends the artifacts; the server makes the authoritative decision.

Geolocation (Art. 4/C(1)(e))​

Geolocation is captured natively, with no third-party plugin — iOS Core Location, Android LocationManager, through the SDK's own platform channel. When a step has collectGeolocation enabled, the SDK takes a single coarse fix on its own; there is no app-side code to write.

The only setup is declaring the OS permission strings from Installation:

  • iOS NSLocationWhenInUseUsageDescription — Core Location shows the system prompt on first use given this string.
  • Android ACCESS_COARSE_LOCATION — the SDK requests it at runtime.

Everything is fail-soft: if location services are off, permission is denied, or the fix times out, the SDK sends nothing and the flow proceeds — the backend treats a missing fix as "not provided" while still recording that it was expected. The fix is rounded to ~3 decimals (~110 m) before sending.

Deep link functionality is an optional feature if you want to publish a standalone app. To process deep links with the SDK, you need to configure deep links in your app and set up the required hooks to handle incoming URLs.

iOS - Add to Info.plist:

<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>yourapp</string>
</array>
</dict>
</array>

Android - Add to AndroidManifest.xml. Keep android:launchMode="singleTop" (the Flutter default) so a link arriving while the app is open is delivered to the existing activity:

<activity android:name=".MainActivity" android:launchMode="singleTop">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="yourapp" />
</intent-filter>
</activity>

Add the app_links package — it delivers both cold-start and warm-start links and registers the native handlers (onNewIntent on Android, the scene/URL callbacks on iOS) itself, so no MainActivity or AppDelegate override is required:

dependencies:
app_links: ^6.3.2

Subscribe to the links and feed the parsed sessionId / baseUrl into TrustchexView. Use DeeplinkUtils.parseQrCodeUrl, which parses the app-url/{host}/verification-session/{id} format the backend generates (including the embedded https:// host form):

import 'dart:async';

import 'package:app_links/app_links.dart';
import 'package:flutter/material.dart';
import 'package:trustchex_flutter_sdk/trustchex_flutter_sdk.dart';

class App extends StatefulWidget {
const App({super.key});

@override
State<App> createState() => _AppState();
}

class _AppState extends State<App> {
final AppLinks _appLinks = AppLinks();
StreamSubscription<Uri>? _linkSubscription;
String _baseUrl = 'https://app.trustchex.com';
String _sessionId = '';
String? _lastHandledUrl;

@override
void initState() {
super.initState();
_initDeepLinks();
}

Future<void> _initDeepLinks() async {
// Cold start: the app was launched by the deep link.
final initialUri = await _appLinks.getInitialLink();
if (initialUri != null) _processDeepLink(initialUri.toString());

// Warm start: the link arrives while the app is already running.
_linkSubscription = _appLinks.uriLinkStream.listen(
(uri) => _processDeepLink(uri.toString()),
onError: (_) {},
);
}

void _processDeepLink(String url) {
if (url == _lastHandledUrl) return; // dedup replays on resume
final parsed = DeeplinkUtils.parseQrCodeUrl(url);
final sessionId = parsed['sessionId'];
final baseUrl = parsed['baseUrl'];
if (sessionId != null &&
sessionId.isNotEmpty &&
baseUrl != null &&
baseUrl.isNotEmpty) {
setState(() {
_lastHandledUrl = url;
_baseUrl = baseUrl;
_sessionId = sessionId;
});
}
}

@override
void dispose() {
_linkSubscription?.cancel();
super.dispose();
}

@override
Widget build(BuildContext context) {
return TrustchexView(
baseUrl: _baseUrl,
sessionId: _sessionId.isEmpty ? null : _sessionId,
onCompleted: () => print('Done'),
onError: (error) => print('Error: $error'),
);
}
}

DeeplinkUtils.parseQrCodeUrl parses URLs with the format:

scheme://app-url/your-api.com/verification-session/session-123

And returns a map with baseUrl and sessionId keys.

Sessions​

Create sessions using the REST API.

Session Access Methods​

The SDK provides multiple ways for users to access their verification session:

Users click a link that opens the app directly with the session:

// Deep link format: yourapp://app-url/your-api.com/verification-session/session-123
TrustchexView(
baseUrl: 'https://your-api.com',
sessionId: 'session-123', // From deep link
onCompleted: () => print('Done'),
)

Benefits:

  • Seamless user experience - one tap to start
  • Direct navigation to verification
  • No manual input required
  • Works great for email and SMS campaigns

Users scan a QR code that contains the session information. The SDK includes a built-in QR scanner accessible from the welcome screen.

Benefits:

  • Quick and contactless
  • No typing required
  • Ideal for in-person verification
  • Built-in scanner included in SDK

3. Session Code Entry (Alternative)​

Users can manually enter an 8-character alphanumeric code:

TrustchexView(
baseUrl: 'https://your-api.com',
// No sessionId prop needed - user enters code manually
onCompleted: () => print('Done'),
)

When no sessionId is provided, the SDK displays a screen where users can:

  • Enter the 8-character session code
  • Scan a QR code

Use Cases:

  • Fallback when QR/deep link unavailable
  • Phone/voice support scenarios
  • Print media without QR capability

Creating Sessions​

When creating a session via the REST API, you'll receive access methods in the response:

final response = await http.post(
Uri.parse('https://your-api.com/api/v1/verification-sessions'),
headers: {'x-api-key': 'YOUR_API_KEY'},
body: jsonEncode({
'workflowId': 'workflow-123',
'email': 'user@example.com'
}),
);

final data = jsonDecode(response.body);
final deepLink = data['deepLink'];
final qrCodeLink = data['qrCodeLink'];
final sessionCode = data['sessionCode'];

// Use deepLink for direct app navigation (recommended)
// Use qrCodeLink to generate QR code (recommended)
// Use sessionCode as alternative access method